/* 
**  File: MenuFuncts.c
**
**  Developed in the NMR Spectroscopy Department, Bijvoet Center for 
**  Biomolecular Research, Utrecht. 
**
**
**  AUTHOR(S) : Ton Rullmann
**  DATE      : Feb 17, 1997
**
**  This file contains routines for handling menu's, i.e. for presenting
**  input options to the user, reading the input (from the keyboard or
**  the command line), and processing the input.
**
**
**  Example of declarations in program:
**  ==================================
**
**  enum { read, select, write };
**  static MenuItem menulist[] =
**      {
**         { item_read,   "read",   none, on },
**         { item_write,  "write",  none, on },
**         { item_select, "select", none, off }, 
**         { item_QUIT,   "quit",   none, on }
**      };
**  static Menu menu =
**  {
**      "Main menu", "command", "Give command:",
**      sizeof(menulist)/sizeof(MenuItem)
**  };
**  
**  Example of use in program:
**  =========================
**
**  Menu    *oldmenu;
**  int     item;
**
**  oldmenu = SetMenu( &menu, menulist );
**  while ( true )
**  {
**      item = ReadMenuItem();
**      switch ( item )
**      {
**          case read:
**              * code *
**              break;
**          case write:
**              * code *
**              break;
**          case select:
**              * code *
**              break;
**          case item_QUIT:
**          case item_NULL:
**              goto end;
**          default:
**              ProcessMenuError( menu_fatal );
**              break;
**      }
**  }
**  end: ResetMenu( oldmenu );
**
**  Further:
**  =======
**  
**  predefined items
**  other routines to use (SetOneMenu, MReadWord, EnableMenuItem, DisableMenuItem)
**  comment processing (loop in MReadWord to intercept blank lines; switching
**    to interactive mode; defining commands_fp; use of '?'; options)
**  ProgramInit should be called before accessing the menu routines.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "Compiler.h"

/* BEGIN_EXTH */
#include "General.h"
#include "MenuTypes.h"
/* END_EXTH */

/****************  function prototypes  ****************/

#include "Functs.h"

#include "MenuFuncts.h"

static void CodeError( char *fct, const int item, Menu *menu );
static void PresentMenu( void );
static Boolean SReadWord( char *word );
static Boolean SReadWord2( char *word, const char delim );
static void PrintOptionItemVal( OptionItem *optitem );
static Boolean ReadOptionItemVal( OptionItem *optitem, char *word );


/****************  globals  ****************/

extern Boolean  iaf, batch;             /* import from Functs */
extern FILE     *commands_fp;           /* import from Functs */
extern int      terseness;              /* import from Functs */

static Menu     *theMenu;
static Menu     *theOptions;
static Card     keyword;
static Boolean  bufempty = true, kbdbufempty = true;


/* BEGIN_EXTH */


/****************  MenuStat  ****************/
/*
**  
*/

void MenuStat( void )
{
    printf( "<MenuStat: iaf=%i, bufempty=%i, kbdbufempty=%i, batch=%i, commands_fp==stdin? %i>\n",
            iaf, bufempty, kbdbufempty, batch, commands_fp==stdin );
}

/****************  DoExec  ****************/
/*
**  Switch to executing commands from a command file.
*/

void DoExec( void )
{
    Menu     *oldmenu;
    char     filnam[80];
    Boolean  done = false;
    FILE     *fp;

    oldmenu = SetOneMenu( "Give name of command file (minus .cmm extension), or - to skip:" );
    while ( !done )
    {
        MReadWord( filnam );
        if ( EQ( filnam, "-" ) )
        {
            ResetMenu( oldmenu );
            return;
        }
        strcat( filnam, ".cmm" );
        if ( ! ( done = OPENr( fp, filnam ) ) )
        {
            printf( " * command file <%s> could not be opened\n", filnam );
            MError();
        }
    }
    commands_fp = fp;
    iaf = true;
    bufempty = false;
    ResetMenu( oldmenu );
}

/****************  SetMenu  ****************/
/*
**  Set the current menu. The menulist is set to <menulist>.
**  All submenu links are set to NULL.
*/

Menu *SetMenu( Menu *menu, MenuItem *menulist )
{
    int         i = 0;
    Menu        *oldmenu;
    MenuItem    *menuitem;
    
    oldmenu = theMenu;
    theMenu = menu;
    menu->items = menulist;

    menuitem = theMenu->items;
    while ( i++ < theMenu->size )
    {
        menuitem->menu = NULL;
        menuitem++;
    }

    return( oldmenu );
}

/****************  SetOneMenu  ****************/
/*
**  Set the current menu to a single prompt.
**  The set/reset mechanism works only to one level deep, because only
**  one 'dummy' menu ever exists.
*/

Menu *SetOneMenu( char *string )
{
    Menu *oldmenu;
    static Menu    menu =
    {
        "dummy menu", "", "", 0, NULL
    };
    
    oldmenu = theMenu;
    theMenu = &menu;
    menu.prompt = string;
    return( oldmenu );
}

/****************  ResetMenu  ****************/
/*
**  Reset the current menu to a previous one.
*/

Menu *ResetMenu( Menu *menu )
{
    Menu *oldmenu;

    oldmenu = theMenu;
    theMenu = menu;
    return( oldmenu );
}

/****************  ReadMenuItem  ****************/
/*
**  Present the current menu, read input, and find the item.
*/

int ReadMenuItem( void )
{
    int item;
    
    PresentMenu();
    MReadWord( keyword );
    if (  *keyword == '?' )
    {
        PresentMenu();
        MReadWord( keyword );
    }
    item = FindMenuItem( keyword );
    return( item );
}

/****************  ProcessMenuError  ****************/
/*
**  The last keyword read was in error. Print a message.
**  Return if <err_level> is menu_warn.
**  Otherwise call MError.
*/

void ProcessMenuError( const int err_level )
{
    if ( err_level == menu_warn )
    {
        printf( "Ignoring %s <%s>\n", theMenu->object, keyword );
        return;
    }
    else
    {
        printf( " * Unknown or disabled %s <%s>\n", theMenu->object, keyword );
        MError();
    }

}

/****************  MError  ****************/
/*
**  If processing a command file the remainder is skipped. Then:
**  if command line arguments are still present they are skipped, or
**  if commands have been typed ahead the remainder is skipped.
**  The program switches to interactive mode, 
**  but exits if <batch> was set and the program was NOT in interactive mode
**  (i.e. was processing the command line).
*/

void MError( void )
{
    if ( commands_fp != stdin )    /* reading from a command file */
    {
        puts( " * Skipping rest of command file" );
        fclose( commands_fp );
        commands_fp = stdin;
    }

    if ( MoreArg() )               /* command line arguments present ? */
    {
        if ( batch ) 
	{
	    puts( " * No more commands available" );
            exit( 1 );             /* prevent switch to interactive if batch set */
	}
        while ( MoreArg() )
            NextArg();             /* skip remainder of command line */
        puts( " * Skipping rest of command line" );
    }


    if ( commands_fp == stdin && !kbdbufempty )
    {
        while ( getchar() != '\n' )
            ;                      /* skip characters "typed ahead" */
        puts( " * Skipping rest of typed commands" );
    }
    
    kbdbufempty = true;
    bufempty = true;
    iaf = true;
}

/****************  MReadText  ****************/
/*
**  Read text demarcated by <delim>, by repeated calls to MReadWord.
**  If <delim> is not encountered, only a single word is returned.
**  At least <size> bytes should be available in <txt>.
**  The routine returns <false> if not enough space is available, 
**  <true> otherwise.
*/

Boolean MReadText( char *txt, size_t size, const char delim )
{
    Boolean  done = false;
    size_t   l;

    *txt = '\0';
    MReadWord( keyword );
    if ( strlen( keyword ) > size-1 )
	return( false );

    if ( *keyword != delim )
    {
	strcpy( txt, keyword );
	return( true );
    }
    strcpy( txt, keyword+1 );
    while ( !done )
    {
	MReadWord( keyword );
	l = strlen( keyword );
	if ( keyword[l-1] == delim )
	{
	    done = true;
	    keyword[l-1] = '\0';
	    l--;
	}
	if ( l + strlen( txt ) > size-2 )
	    return( false );
	strcat( txt, " " );
	strcat( txt, keyword );
    }
    return( true );
}

/****************  MReadText2  ****************/
/*
**  Read text demarcated by <delim>, by calling MReadWord2.
**  At least <size> bytes should be available in <txt>.
**  The routine returns <false> if not enough space is available, 
**  <true> otherwise.
*/

Boolean MReadText2( char *txt, size_t size, const char delim )
{
    *txt = '\0';
    
    if ( MoreArg() )        /* quick fix to handle text from command line - need to improve SReadWord2 */
	return( MReadText( txt, size, delim ) );

    MReadWord2( keyword, delim );
    if ( strlen( keyword ) > size-1 )
	return( false );
    strcpy( txt, keyword );
    return( true );
}

/****************  MReadWord  ****************/
/*
**  Present the current menu prompt and call SReadWord.
*/

void MReadWord( char *word )
{
    Boolean  eof;
    Card     comm;

    do
    {
        if ( bufempty && iaf && commands_fp == stdin )
            printf( "%s ", theMenu->prompt );
        eof = SReadWord( word );
        if ( eof )
        {
            if ( commands_fp != stdin )
                fclose( commands_fp );
	    else
		break;            /* typing ctrl-D now works */
            commands_fp = stdin;
            if ( !iaf && !MoreArg() && batch )
	    {
		puts( " * No more commands available" );
                exit( 1 );       /* prevent switch to interactive if batch set */
	    }
            iaf = !MoreArg();
            if ( !iaf )
                bufempty = false;
            else
                bufempty = kbdbufempty;
        }
	if ( *word == '!' )
	{
	    if ( MoreArg() )
	    {
		puts( " * ! command cannot be used on the command line" );
		MError(); 
	    }
	    else
	    {
		strcpy( comm, &word[1] );
		strcat( comm, " " );
		if ( !bufempty )
		{
		    ReadLine( &comm[strlen(comm)], size_LINE, commands_fp );
		    bufempty = true;
		    if ( commands_fp == stdin ) kbdbufempty = true;
		}
		system( comm );
	    }
	    *word = '\0';
	}
    }
    while
        ( strlen( word ) == 0 );
#ifdef DEBUG
    printf( "<MReadWord end: menu word read: %s>\n", word );
#else
    if ( terseness < 6 ) {
        printf( "<Command read: %s>\n", word );
    }
#endif
#ifdef DEBUGLONG
    MenuStat();
#endif
}

/****************  MReadWord2  ****************/
/*
**  Present the current menu prompt and call SReadWord2.
*/

void MReadWord2( char *word, const char delim )
{
    Boolean  eof;
    Card     comm;

    do
    {
        if ( bufempty && iaf && commands_fp == stdin )
            printf( "%s ", theMenu->prompt );
        eof = SReadWord2( word, delim );
        if ( eof )
        {
            if ( commands_fp != stdin )
                fclose( commands_fp );
	    else
		break;            /* typing ctrl-D now works */
            commands_fp = stdin;
            if ( !iaf && !MoreArg() && batch )
	    {
		puts( " * No more commands available" );
                exit( 1 );       /* prevent switch to interactive if batch set */
	    }
            iaf = !MoreArg();
            if ( !iaf )
                bufempty = false;
            else
                bufempty = kbdbufempty;
        }
	if ( *word == '!' )
	{
	    if ( MoreArg() )
	    {
		puts( " * ! command cannot be used on the command line" );
		MError(); 
	    }
	    else
	    {
		strcpy( comm, &word[1] );
		strcat( comm, " " );
		if ( !bufempty )
		{
		    ReadLine( &comm[strlen(comm)], size_LINE, commands_fp );
		    bufempty = true;
		    if ( commands_fp == stdin ) kbdbufempty = true;
		}
		system( comm );
	    }
	    *word = '\0';
	}
    }
    while
        ( strlen( word ) == 0 );
#ifdef DEBUG
    printf( "<MReadWord2 end: menu word read: %s>\n", word );
#else
    printf( "<Command read: %s>\n", word );
#endif
#ifdef DEBUGLONG
    MenuStat();
#endif
}

/****************  EnableMenuItem  ****************/
/*
**  Make a menu item active, i.e. available to the user.
*/

void EnableMenuItem( const int item )
{
    int       i = 0;
    MenuItem  *menuitem = theMenu->items;
    
    while ( i++ < theMenu->size )
    {
        if ( menuitem->item == item )
        {
            menuitem->status = on;
            return;
        }
        menuitem++;
    }
    CodeError( "EnableMenuItem", item, theMenu );
}

/****************  DisableMenuItem  ****************/
/*
**  Make a menu item not active, i.e. not available to the user.
**  Returns true if the operation was succesfull, otherwise false.
*/

void DisableMenuItem( const int item )
{
    int       i = 0;
    MenuItem  *menuitem = theMenu->items;
    
    while ( i++ < theMenu->size )
    {
        if ( menuitem->item == item )
        {
            menuitem->status = off;
            return;
        }
        menuitem++;
    }
    CodeError( "DisableMenuItem", item, theMenu );
}

/****************  FindMenuItemPtr  ****************/
/*
**  Find active menu item of which the key is equal to the string 'word'.
**  Return a pointer to the menu item.
*/

MenuItem *FindMenuItemPtr( char *word )
{
    int       i = 0;
    MenuItem  *item = theMenu->items;
    
    if ( *word == '\0' )
        return( NULL );
    
    while ( i++ < theMenu->size )
    {
        if ( strcmp( word, item->key ) == 0 && item->status == on )
            return( item );
        item++;
    }

    return( NULL );
}

/****************  FindMenuItem  ****************/
/*
**  Find active menu item of which the key is equal to the string 'word'.
**  Return the item value.
*/

int FindMenuItem( char *word )
{
    int       i = 0;
    MenuItem  *item = theMenu->items;
    
    if ( *word == '\0' )
        return( item_NULL );
    
    while ( i++ < theMenu->size )
    {
        if ( strcmp( word, item->key ) == 0 && item->status == on )
            return( item->item );
        item++;
    }

    return( item_ERROR );
}

/****************  GetMenuItemKey  ****************/
/*
**  Return a pointer to the key of the menu item.
*/

char *GetMenuItemKey( const int item )
{
    int       i = 0;
    MenuItem  *menuitem = theMenu->items;
    
    while ( i++ < theMenu->size )
    {
        if ( menuitem->item == item )
            return( menuitem->key );
        menuitem++;
    }
    CodeError( "GetMenuItemKey", item, theMenu );
    return( NULL ); /* Will never be executed */
}

/****************  GetMenuItemPtr  ****************/
/*
**  Return a pointer to the menu item.
*/

MenuItem *GetMenuItemPtr( const int item )
{
    int       i = 0;
    MenuItem  *menuitem = theMenu->items;
    
    while ( i++ < theMenu->size )
    {
        if ( menuitem->item == item )
            return( menuitem );
        menuitem++;
    }
    CodeError( "GetMenuItemPtr", item, theMenu );
    return( NULL ); /* Will never be executed */
}

/****************  SetSubMenu  ****************/
/*
**  Set the submenu belonging to menu item 'item' in 'menu'.
**  The main menu must have been initialized properly through SetMenu
**  or SetOptions.
**  If a menu is set for the second time through SetMenu or SetOptions, 
**  also SetSubMenu calls have to be repeated after that.
**  Presently, a submenu of a menu has no meaning. Submenus are only used in
**  conjunction with Options.
*/

void SetSubMenu( Menu *mainmenu, const int item, Menu *menu, MenuItem *menulist )
{
    int         i = 0;
    MenuItem    *menuitem = mainmenu->items;

    while ( i++ < mainmenu->size )
    {
        if ( menuitem->item == item )
        {
            menuitem->menu = menu;
            menuitem->menu->items = menulist;
            return;
        }
        menuitem++;
    }
    CodeError( "SetSubMenu", item, mainmenu );
    exit( 1 ); /* Will never be executed */
}

/****************  SetOptions  ****************/
/*
**  Set the current options menu. The menulist is set to <optionlist>.
**  All submenu links are set to NULL.
**  Options are very similar to menus, but in addition they have item
**  typing, value checking, and automatic presentation of submenus.
**  (See Readoption, ModOption, ReadOptionItemVal).
*/

Menu *SetOptions( Menu *options, MenuItem *optionlist )
{
    char        *fct_name = "SetOptions";
    int         i = 0;
    Menu        *oldoptions;
    OptionItem  *optitem;
    
    oldoptions = theOptions;
    theOptions = options;
    options->items = optionlist;

    optitem = theOptions->items;
    while ( i++ < theOptions->size )
    {
        switch ( optitem->type )
        {
            case logical:
            case integer:
                optitem->value.i = 0;
                break;
            case real:
                optitem->value.r = 0.F;
                break;
            case oneword:
                optitem->word[0] = '\0';
                break;
            case string:
                optitem->value.s = NULL;
                break;
            default:
                FATAL("Impossible code error");
        }
        optitem->word[0] = '\0';
        optitem->menu = NULL;
#ifdef DEBUG
        printf( "<SetOption: %i %s>\n", optitem->item, optitem->key );
#endif
        optitem++;
    }

    return( oldoptions );
}

/****************  ReadOption  ****************/
/*
**  Read an option and its value, using the menu mechanism.
*/

Boolean ReadOption( void )
{
    int         item;
    Menu        *oldmenu;
    OptionItem  *optitem;

    oldmenu = theMenu;
    theMenu = theOptions;
    while ( true )
    {
        item = ReadMenuItem();
        switch ( item )
        {
            case item_ERROR:
                ProcessMenuError( menu_warn );     /* non-fatal error */
                if ( !bufempty )
                {
#ifdef DEBUG
                    puts( "<ReadOption: skipping next menu word>" );
#endif
                    MReadWord( keyword );          /* skip the keyword */
                }
                ResetMenu( oldmenu );
                return( false );
            case item_NULL:
                ProcessMenuError( menu_fatal );    /* fatal error */
                ResetMenu( oldmenu );
                return( false );
	    case item_END:
                ResetMenu( oldmenu );
		return( false );
            default:
                optitem = GetMenuItemPtr( item );
                goto data;
        }
    }
    
data:

    if ( bufempty ) 
        PrintOptionItemVal( optitem );

/* show the submenu */

    if ( optitem->menu )
    {
        SetMenu( optitem->menu, optitem->menu->items );
        PresentMenu();
    }
    else
        SetOneMenu( "Give value:" );

/* read and check the data */

    MReadWord( keyword );
    if ( !ReadOptionItemVal( optitem, keyword ) )
    {
        printf( " * Error reading value from <%s>\n", keyword );
        MError();
    }
    PrintOptionItemVal( optitem );

    ResetMenu( oldmenu );
    return( true );
}

/****************  ModOption  ****************/
/*
**  Modify the value of an option.
*/

void ModOption( const int item, char *word )
{
    int         i = 0;
    OptionItem  *optitem = theOptions->items;

#ifdef DEBUG
    printf( "<ModOption: want to change option %i to %s>\n", item, word );
#endif

    while ( i++ < theOptions->size )
    {
#ifdef DEBUG
        printf( "<ModOption: %i %s>\n", optitem->item, optitem->key );
#endif
        if ( optitem->item == item )
        {
            if ( ReadOptionItemVal( optitem, word ) )
            {
                PrintOptionItemVal( optitem );
                return;
            }
            printf( " * CANNOT READ VALUE from <%s> for item %i in ModOption\n", word, item );
            exit( 1 );
        }
        optitem++;
    }
    CodeError( "ModOption", item, theOptions );
    exit( 1 ); /* Will never be executed */    
}

/****************  PrintOptionVal  ****************/
/*
**  Print the value of the option with item value 'item'.
*/

void PrintOptionVal( const int item )
{
    int         i = 0;
    OptionItem  *optitem = theOptions->items;

    while ( i++ < theOptions->size )
    {
        if ( optitem->item == item )
        {
            PrintOptionItemVal( optitem );
            return;
        }
        optitem++;
    }
    CodeError( "PrintOptionVal", item, theOptions );
    exit( 1 ); /* Will never be executed */
}

/****************  GetOptionSubItem  ****************/
/*
**  Return the submenu-item value of the option with item value 'item'.
*/

int GetOptionSubItem( const int item )
{
    int         i = 0;
    OptionItem  *optitem = theOptions->items;

    while ( i++ < theOptions->size )
    {
        if ( optitem->item == item )
            return( optitem->subitem );
        optitem++;
    }
    CodeError( "GetOptionSubItem", item, theOptions );
    return(1); /* Will never be executed */
}

/****************  GetOptionRVal  ****************/
/*
**  Return the value of the option with item value 'item' and type real.
*/

float GetOptionRVal( const int item )
{
    int         i = 0;
    OptionItem  *optitem = theOptions->items;

    while ( i++ < theOptions->size )
    {
        if ( optitem->item == item && optitem->type == real )
            return( optitem->value.r );
        optitem++;
    }
    CodeError( "GetOptionRVal", item, theOptions );
    return(-999.99); /* Will never be executed */
}

/****************  GetOptionIVal  ****************/
/*
**  Return the value of the option with item value 'item' and type integer.
*/

int GetOptionIVal( const int item )
{
    int         i = 0;
    OptionItem  *optitem = theOptions->items;

    while ( i++ < theOptions->size )
    {
        if ( optitem->item == item && optitem->type == integer )
            return( optitem->value.i );
        optitem++;
    }
    CodeError( "GetOptionIVal", item, theOptions );
    return(-999); /* Will never be executed */
}

/****************  GetOptionCVal  ****************/
/*
**  Return the value of the option with item value 'item' and type 
**  oneword or string. The return value is a pointer to a character string.
*/

char *GetOptionCVal( const int item )
{
    int         i = 0;
    OptionItem  *optitem = theOptions->items;

    while ( i++ < theOptions->size )
    {
        if ( optitem->item == item && optitem->type == oneword )
            return( optitem->word );
        if ( optitem->item == item && optitem->type == string )
            return( optitem->value.s );
        optitem++;
    }
    CodeError( "GetOptionCVal", item, theOptions );
    return( NULL ); /* Will never be executed */
}

/****************  OptionTrue  ****************/
/*
**  Check the option with item value 'item'. Returns 'true' if:
**    - type logical or integer: value true
**    - type real:               value not 0.
**    - type oneword or string:  value not empty string
**
*/

Boolean OptionTrue( const int item )
{
    int         i = 0;
    OptionItem  *optitem = theOptions->items;
    while ( i++ < theOptions->size )
    {
        if ( optitem->item == item )
        {
            if ( optitem->type == logical || optitem->type == integer )
                return( optitem->value.i != 0 );
            if ( optitem->type == real )
                return( optitem->value.r != 0.F );
            if ( optitem->type == oneword )
                return( LenChars( optitem->word, size_OPTIONWORD ) != 0 );
            if ( optitem->type == string )
            {
                if ( optitem->value.s )
                    return( !IsEmptyStr( optitem->value.s ) );
                else
                    return( false );
            }
        }
        optitem++;
    }
    CodeError( "OptionTrue", item, theOptions );
    return( false ); /* Will never be executed */
}


/* END_EXTH ----------------------- only private functions follow */

/****************  CodeError  ****************/
/*
**  Fatal error.
*/

static void CodeError( char *fct, const int item, Menu *menu )
{
    printf( " * CODE ERROR in %s: item %i not found in %s\n", 
            fct, item, menu->title );
    exit( 1 );
}

/****************  PresentMenu  ****************/
/*
**  Present the current menu.
*/

static void PresentMenu( void )
{
    int       i = 0, first = true;
    MenuItem  *item = theMenu->items;
    
#ifndef DEBUG
    if ( !bufempty ) 
        return;
#endif

    if ( batch && ( terseness == 9 ) ) {
        return;
    }
    
    printf( "%s - choose %s from:", theMenu->title, theMenu->object );
    while ( i++ < theMenu->size )
    {
        if ( item->status == on )
        {
            if ( !first )
                putchar( ',' );
            printf( " %s", item->key );
            first = false;
        }
        item++;
    }
    putchar( '\n' );
}

/****************  SReadWord  ****************/
/*
**  If <iaf> is false, then:
**  copy successive command line arguments into <word> and set <bufempty>
**  to false until all command line arguments are exhausted, 
**  and then switch to interactive mode (<iaf> true, <bufempty> true).
**  This switch is not performed if <batch> is true.
**
**  If <iaf> is true, then:
**  read <word>, i.e. string separated by whitespace, from <commands_fp>.
**  A null character is appended. The routine attempts reading past the
**  word. <bufempty> is then set to false if characters other than
**  a space or a tab are encountered.
**
**  Returns true if EOF was read, otherwise false.
**  No test for the available space is performed!
**
**  In all circumstances <bufempty> indicates whether or not more 
**  characters are available (either on command line, on file, 
**  or "typed ahead") ** BUT ** this is only checked upto a newline.
**  <kbdbufempty> retains the <bufempty> value for keyboard input only
**  (i.e. when <commands_fp> == stdin).
*/

static Boolean SReadWord( char *word )
{
    int     c = 0;
    char    *w;
    Boolean done = false, inword = false, stop = false;

#ifdef DEBUGLONG
    puts( "<SReadWord begin:>" );
    MenuStat();
#endif

    if ( !iaf ) 
    {
        if ( ( w = NextArg() ) )
        {
            strcpy( word, w );
            bufempty = !MoreArg();
            return( false );
        }
        else
        {
            *word = '\0';
            if ( batch )
	    {
		puts( " * No more commands available" );
                exit( 1 );                  /* no iaf mode if batch was set */
	    }
            iaf = true;
            bufempty = true;                /* change to interactive mode */
            return( false );
        }
    }


    while ( !done )
    {
        c = fgetc( commands_fp );                   /* get character */
        if ( c != EOF && !isspace( c ) )            /* skip whitespace */
        {
            inword = true;                          /* now storing */
            *word++ = c;
        }
        else
        {
            stop = ( c == EOF || ( c != ' ' && c != '\t' ) );
                                                    /* stop at whitespace 
                                                       unless tab or 
                                                       space was read */
            done = ( inword || stop );              /* if storing, end it */
        }
    }
    *word = '\0';                                   /* add null-character */

    if ( stop )
    {
        bufempty = true;
        if ( commands_fp == stdin ) kbdbufempty = true;

        return( c == EOF );                         /* return if stop ... */
    }
    
    while ( !stop )                                 /* ... and else ... */
    {
        c = fgetc( commands_fp );                   /* ... skip remaining
                                                    tabs and spaces */
        stop = ( c == EOF || ( c != ' ' && c != '\t' ) );
    }
    
    if ( c == EOF || isspace( c ) )
    {
        bufempty = true;                            /* only whitespace left */
        if ( commands_fp == stdin ) kbdbufempty = true;
    }
    else
    {
        ungetc( c, commands_fp );                   /* put back 'real' character */
        bufempty = false;        
        if ( commands_fp == stdin ) kbdbufempty = false;
    }

    return( c == EOF );
}

/****************  SReadWord2  ****************/
/*
**  Variation on SReadWord:
**  if word starts with <delim>, read until second <delim>, keeping internal
**  whitespace if possible.
**  (This does not work if command line arguments are being processed!). 
*/

static Boolean SReadWord2( char *word, const char delim )
{
    int     c = 0;
    char    *w, escap = '\\', lstchr = '\0';
    Boolean done = false, inword = false, stop = false;

#ifdef DEBUGLONG
    puts( "<SReadWord2 begin:>" );
    MenuStat();
#endif

    if ( !iaf ) 
    {
        if ( ( w = NextArg() ) ) 
        {
            strcpy( word, w );
            bufempty = !MoreArg();
            return( false );
        }
        else
        {
            *word = '\0';
            if ( batch )
	    {
		puts( " * No more commands available" );
                exit( 1 );                  /* no iaf mode if batch was set */
	    }
            iaf = true;
            bufempty = true;                /* change to interactive mode */
            return( false );
        }
    }


    while ( !done )
    {
        c = fgetc( commands_fp );                   /* get character */
        if ( c != EOF && 
	      !( !inword && isspace( c ) ) )        /* skip initial whitespace */
        {
	    if ( c == delim )
	    {
		if ( lstchr != escap )
		    inword = !inword;               /* switch storing mode */
		else
		{
		    lstchr = c;                     /* store the escape'd character */
		    *(word-1) = c;
		}		    
	    }
            else
	    {
		lstchr = c;
		*word++ = c;                        /* if no delimiter is used
		                                       any non-whitespace is
						       stored */
	    }
        }
        else
        {
            stop = ( c == EOF || ( c != ' ' && c != '\t' ) );
                                                    /* stop at whitespace 
                                                       unless tab or 
                                                       space was read */
            done = ( inword || stop );              /* if storing, end it */
        }
    }
    *word = '\0';                                   /* add null-character */

    if ( stop )
    {
        bufempty = true;
        if ( commands_fp == stdin ) kbdbufempty = true;

        return( c == EOF );                         /* return if stop ... */
    }
    
    while ( !stop )                                 /* ... and else ... */
    {
        c = fgetc( commands_fp );                   /* ... skip remaining
                                                    tabs and spaces */
        stop = ( c == EOF || ( c != ' ' && c != '\t' ) );
    }
    
    if ( c == EOF || isspace( c ) )
    {
        bufempty = true;                            /* only whitespace left */
        if ( commands_fp == stdin ) kbdbufempty = true;
    }
    else
    {
        ungetc( c, commands_fp );                   /* put back 'real' character */
        bufempty = false;        
        if ( commands_fp == stdin ) kbdbufempty = false;
    }

    return( c == EOF );
}

/****************  PrintOptionItemVal  ****************/
/*
**  Print the value of an OptionItem.
*/

static void PrintOptionItemVal( OptionItem *optitem )
{
    char        *fct_name = "PrintOptionItemVal";

    if ( terseness < 6 ) {

         switch ( optitem->type )
         {
             case logical:
                 printf( "Option <%s> : <%s>\n", optitem->key, 
                     optitem->value.i == 0 ? "false" : "true" );
                 break;
             case integer:
                 printf( "Option <%s> (integer): <%i>\n", optitem->key, optitem->value.i );
                 break;
             case real:
                 printf( "Option <%s> (real): <%f>\n", optitem->key, optitem->value.r );
                 break;
             case oneword:
                 printf( "Option <%s> (word): <%s>\n", optitem->key, optitem->word );
                 break;
             case string:
                 printf( "Option <%s> (string): <%s>\n", optitem->key, optitem->value.s );
                 break;
             default:
                 FATAL("Unforeseen code error");
         }

    }
}

/****************  ReadOptionItemVal  ****************/
/*
**  Read the value of an OptionItem from <word>
**  and check its validity.
*/

static Boolean ReadOptionItemVal( OptionItem *optitem, char *word )
{
    char     *fct_name = "ReadOptionItemVal";
    Boolean  ok = true;
    Menu     *menu = optitem->menu;
    char     *saveword, *tmpword;
    int      isub, ival;
    float    rval;

/* check the data word against a submenu, if present */
/* implement range check: menu items of form "a..b" */

    if ( menu )
    {
        if ( *word == '\0' )
            return( false );
        for ( isub=0; isub<menu->size; isub++ )
	{
#ifdef DEBUG
	    printf( "<ReadOptionItemVal: isub %i %s>\n", isub, menu->items[isub].key );
#endif
            if ( strcmp( word, menu->items[isub].key ) == 0 
                 && menu->items[isub].status == on )
                goto store;
	}
        return( false );
    }
    else
    {
	isub = item_NULL;
    }
    
/* now read, check and store the value */

store:

#ifdef DEBUG
    printf( "<ReadOptionItemVal: isub %i, optitem->type %i>\n", isub, optitem->type );
#endif
    switch ( optitem->type )
    {
        case logical:
        {
            if ( sscanf( word, "%i", &ival ) < 1 )
            {
		tmpword = CREATE_NEW( char, strlen( word ) + 1 );
		strcpy( tmpword, word );
                ToUpperStr( tmpword );
                if ( EQ( tmpword, "T" ) || EQ( tmpword, "TRUE" ) || EQ( tmpword, "ON" ) )
                    optitem->value.i = true;
                else if ( EQ( tmpword, "F" ) || EQ( tmpword, "FALSE" ) || EQ( tmpword, "OFF" ) )
                    optitem->value.i = false;
                else
                    ok = false;
		free( tmpword );
            }
            else
                optitem->value.i = ival;
            break;
        }

        case integer:
        {
            ok = sscanf( word, "%i", &ival ) == 1;
            if ( ok )
                optitem->value.i = ival;
            break;
        }

        case real:
        {
            ok = sscanf( word, "%f", &rval ) == 1;
            if ( ok )
                optitem->value.r = rval;
            break;
        }

        case oneword:
        {
/* store a word - if too long, then switch to string mode */
            if ( strlen( word ) > size_OPTIONWORD )
            {
                saveword = CREATE_NEW( char, strlen( word ) );
                if ( !saveword )
		    NOMEM( "" );
                strcpy( saveword, word );
                optitem->value.s = saveword;
                optitem->type = string;
            }
            else
                strcpy( optitem->word, word );
            break;
        }

        case string:
/* store a pointer to a permanent copy of the word */
        {
            if ( optitem->value.s ) 
		free( optitem->value.s );
            saveword = CREATE_NEW( char, strlen( word ) );
            if ( !saveword )
		NOMEM( "" );
            strcpy( saveword, word );
            optitem->value.s = saveword;
            break;
        }
        default:
            FATAL("Unexpected default in code");
    }

    if ( ok )
	optitem->subitem = isub;
    return( ok );
}
